package org.jeecg.attachment.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.jeecg.attachment.constant.AttachmentConstant;
import org.jeecg.attachment.dto.req.DownloadFileReqDto;
import org.jeecg.attachment.dto.req.UploadFileModel;
import org.jeecg.attachment.dto.util.AttachmentUtil;
import org.jeecg.attachment.entity.SysAttachment;
import org.jeecg.attachment.mapper.SysAttachmentMapper;
import org.jeecg.attachment.service.ISysAttachmentService;
import org.jeecg.common.MessageSNUtil;
import org.jeecg.common.RedisCacheUtil;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.constant.enums.BoolInt;
import org.jeecg.common.exception.JeecgBootException;
import org.jeecg.common.system.vo.DictModel;
import org.jeecg.common.util.DataUtil;
import org.jeecg.common.util.DateUtils;
import org.jeecg.common.util.FileUtil;
import org.jeecg.common.util.SftpUtil;
import org.jeecg.common.util.security.SecurityUtil;
import org.jeecg.config.props.SftpConfig;
import org.jeecg.modules.system.service.ISysDictService;
import org.jeecg.modules.system.service.ISysUserService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 攻防工具附件表
 * @author: qiang.zhou
 * @date: 2024/9/6 10:17
 **/
@Slf4j
@Service
public class SysAttachmentServiceImpl extends ServiceImpl<SysAttachmentMapper, SysAttachment> implements ISysAttachmentService {

    @Resource
    private SftpConfig sftpConfig;
    @Resource
    private RedisCacheUtil redisCacheUtil;
    @Resource
    private ISysDictService sysDictService;
    @Resource
    private ISysUserService sysUserService;

    @Value("${server.servlet.context-path:}")
    private String contextPath;

    @Value("${tender.txtJsonPath}")
    private String txtJsonPath;

    private final static String DOWNLOAD_FILE_SESSION_KEY = "DOWNLOAD_FILE_SESSION_KEY";

    @Override
    public Result<Long> uploadFile(UploadFileModel fileModel) {
        MultipartFile file = fileModel.getFile();

        if (!this.checkUploadFile(file.getOriginalFilename())) {
            return Result.error("不支持的附件类型");
        }

        // 批量上传工具时, 只支持zip格式的压缩包
        if (BoolInt.TRUE.checkCode(fileModel.getType())
                && !file.getOriginalFilename().endsWith(AttachmentConstant.ZIP)) {
            return Result.error("批量上传工具时, 只支持zip格式的压缩包");
        }

        try {
            return this.uploadFile(file.getSize(), SecurityUtil.getUserId(), file.getOriginalFilename(),
                    fileModel.getMd5(), MessageSNUtil.generateFileMessageSN(), file.getInputStream());
        } catch (Exception e) {
            log.warn("文件上传失败, 网络中断: ", e);
            return Result.error("文件上传失败, 网络中断");
        }

    }

    @Override
    public Result<Long> uploadFile(long fileSize, String userId, String fileName, String md5, String fileNo, InputStream inputStream) {
        SftpUtil sftpUtil = null;
        try {
            // 根据md5检查是否有此文件
            SysAttachment attachment = super.lambdaQuery()
                    .eq(SysAttachment::getMd5, md5).last("LIMIT 1").one();
            // 有这个文件 且 已经完成上传，那么只在数据库中保存一笔文件记录
            if (null != attachment && BoolInt.TRUE.checkCode(attachment.getUploadFull())) {
                SysAttachment newAttachment = AttachmentUtil.buildSysAttachment(fileSize, userId, md5, fileName, fileNo, attachment);
                super.save(newAttachment);
                return Result.OK(newAttachment.getId());
            }

            String fileDirectory;
            if (null != attachment && BoolInt.FALSE.checkCode(attachment.getUploadFull())) {
                fileDirectory = attachment.getFileUrl();
            } else {
                // 没有则保存
                attachment = AttachmentUtil.buildSysAttachment(userId, getFileDirectory(fileName), fileName, md5, fileNo, fileSize);
                super.save(attachment);
                fileDirectory = attachment.getFileUrl();
            }
            // 开始上传文件
            sftpUtil = SftpUtil.getInstance(sftpConfig.getTenderSftpConfig());
            sftpUtil.uploadSlice(fileDirectory, md5, fileSize, inputStream);

            // 上传文件完成后修改文件的完成状态
            super.lambdaUpdate()
                    .set(SysAttachment::getUploadFull, BoolInt.TRUE.getCode())
                    .set(SysAttachment::getUpdateTime, new Date())
                    .set(SysAttachment::getUpdateBy, userId)
                    .eq(SysAttachment::getId, attachment.getId())
                    .update();
            return Result.ok(attachment.getId());
        } catch (Exception e) {
            log.warn("文件上传失败, 错误原因: ", e);
            return Result.error("文件上传失败，请重试");
        } finally {
            if (null != sftpUtil) {
                sftpUtil.logout();
            }
            try {
                inputStream.close();
            } catch (IOException e) {
                log.warn("关闭流失败");
            }
        }
    }

    @Override
    public Result<Long> uploadTendFile(long fileSize, String userId, String fileName, String fileNo, InputStream inputStream) {
        SftpUtil sftpUtil = null;
        try {
            SysAttachment attachment = AttachmentUtil.buildSysAttachment(userId, getFileDirectory(fileName), fileName, fileName, fileNo, fileSize);
            super.save(attachment);
            String fileDirectory = attachment.getFileUrl();
            // 开始上传文件
            sftpUtil = SftpUtil.getInstance(sftpConfig.getTenderSftpConfig());
            sftpUtil.uploadSlice(fileDirectory, fileName, fileSize, inputStream);

            // 上传文件完成后修改文件的完成状态
            super.lambdaUpdate()
                    .set(SysAttachment::getUploadFull, BoolInt.TRUE.getCode())
                    .set(SysAttachment::getUpdateTime, new Date())
                    .set(SysAttachment::getUpdateBy, userId)
                    .eq(SysAttachment::getId, attachment.getId())
                    .update();
            return Result.ok(attachment.getId());
        } catch (Exception e) {
            log.warn("文件上传失败, 错误原因: ", e);
            return Result.error("文件上传失败，请重试");
        } finally {
            if (null != sftpUtil) {
                sftpUtil.logout();
            }
            try {
                inputStream.close();
            } catch (IOException e) {
                log.warn("关闭流失败");
            }
        }
    }

    @Override
    public Result<Long> uploadFile(File file, String userId, String fileNo) {
        // 只能上传文件
        if (!file.exists() || !file.isFile()) {
            return Result.error("文件不存在或者不是一个文件");
        }

        try (FileInputStream fis = new FileInputStream(file)) {
            return this.uploadFile(file.length(), userId, file.getName(), FileUtil.md5(file), fileNo, fis);
        } catch (Exception e) {
            log.warn("文件上传失败; ", e);
            return Result.error("文件上传失败，请重试");
        }
    }

    @Override
    public Result<Long> uploadTendFile(File file, String userId, String fileNo) {
        // 只能上传文件
        if (!file.exists() || !file.isFile()) {
            return Result.error("文件不存在或者不是一个文件");
        }

        try (FileInputStream fis = new FileInputStream(file)) {
            return this.uploadTendFile(file.length(), userId, file.getName(), fileNo, fis);
        } catch (Exception e) {
            log.warn("文件上传失败; ", e);
            return Result.error("文件上传失败，请重试");
        }
    }

    @Override
    public Result<Long> uploadTendFile(String txtJson, String startTime, String endTime) {
        String filename = txtJsonPath + startTime.concat("-").concat(endTime).concat(".json");
        File file = new File(filename);
        try (FileWriter writer = new FileWriter(file)) {
            writer.write(txtJson);
        }catch (Exception e){
            Result.error("txtJson保存失败");
        }
        return uploadTendFile(file, "system", MessageSNUtil.generateFileMessageSN());
    }

    private String getFileDirectory(String fileName) {
        String root = sftpConfig.getTenderSftpConfig().getSftpRoot();
        // 在sftp路径为：soc文件目录+当前日期+文件扩展地址+文件md5值
        return String.format("%s%s/%s", root, DateUtils.getCurrentDate(), FileUtil.getFileType(fileName, AttachmentConstant.DEFAULT_FILE_TYPE));
    }

    @Override
    public void downloadFile(String fileToken, HttpServletResponse response) {
        SftpUtil downloadSftp = null;
        DownloadFileReqDto reqDto = redisCacheUtil.hget(DOWNLOAD_FILE_SESSION_KEY, fileToken, DownloadFileReqDto.class);
        if (null == reqDto) {
            log.warn("未知的附件token");
            return;
        }
        if (AttachmentConstant.DownUseStatus.IN.checkCode(reqDto.getStatus())) {
            log.warn("当前附件token已被使用");
            return;
        }

        try {
            // 修改状态
            reqDto.setStatus(AttachmentConstant.DownUseStatus.IN.getCode());
            redisCacheUtil.hset(DOWNLOAD_FILE_SESSION_KEY, fileToken, reqDto);

            downloadSftp = SftpUtil.getInstance(sftpConfig.getTenderSftpConfig());
            SysAttachment attachment = super.getById(reqDto.getId());

            // 下载计数加一
            super.baseMapper.incrDownloadTimes(reqDto.getId());

            // 从sftp响应流
            response.reset();
            response.setContentType("application/octet-stream");
            response.setCharacterEncoding("utf-8");
            response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(attachment.getFileName(),"UTF-8"));
            downloadSftp.download(attachment.getFileUrl(), attachment.getMd5(), response);
            response.flushBuffer();
        } catch (Exception e) {
            log.warn("文件下载失败;", e);
        } finally {
            // 附件下载完成后删除
            redisCacheUtil.hdel(DOWNLOAD_FILE_SESSION_KEY, fileToken);

            if (null != downloadSftp) {
                downloadSftp.logout();
            }
        }
    }

    @Override
    public void downloadImage(DownloadFileReqDto reqDto, HttpServletResponse response) throws Exception {
        SftpUtil downloadSftp = SftpUtil.getInstance(sftpConfig.getTenderSftpConfig());

        try {
            SysAttachment attachment = super.getById(reqDto.getId());
            if (null == attachment) {
                throw new JeecgBootException("未找到附件");
            }

//            if (!checkImage(attachment)) {
//                throw new JeecgBootException("只能下载图片类型的附件");
//            }

            if (BoolInt.FALSE.checkCode(attachment.getUploadFull())) {
                throw new JeecgBootException("附件未上传完成，请稍后");
            }

            // 从sftp响应流
            response.reset();
            response.setContentType("application/octet-stream");
            response.setCharacterEncoding("utf-8");
            response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(attachment.getFileName(),"UTF-8"));
            downloadSftp.download(attachment.getFileUrl(), attachment.getMd5(), response);
            response.flushBuffer();
        } finally {
            downloadSftp.logout();
        }
    }

    private boolean checkImage(SysAttachment attachment) {
        List<DictModel> dictList = sysDictService.queryDictItemsByCode(AttachmentConstant.DICT_IMAGE_TYPE);
        return dictList.stream().anyMatch(dict -> dict.getText().equals(attachment.getFileType()));
    }

    private boolean checkUploadFile(String fileName) {
        String type = FileUtil.getFileType(fileName, "");

        List<DictModel> dictList = sysDictService.queryDictItemsByCode(AttachmentConstant.DICT_UPLOAD_TYPE);
        return dictList.stream().anyMatch(dict -> dict.getText().equals(type));
    }

    @Override
    public Result<String> getFileUrl(DownloadFileReqDto reqDto) {
        // 查询文件
        SysAttachment attachment = super.getById(reqDto.getId());
        if (null == attachment) {
            return Result.error("不存在的附件");
        }
        if (BoolInt.FALSE.checkCode(attachment.getUploadFull())) {
            return Result.error("附件未上传完成，请稍后");
        }

        // 检查sftp服务器上是否存在此文件
        SftpUtil sftpUtil = SftpUtil.getInstance(sftpConfig.getTenderSftpConfig());
        boolean flag = sftpUtil.fileExist(attachment.getFileUrl(), attachment.getMd5());
        if (!flag) {
            return Result.error("不存在的附件");
        }

        // 生成一个临时id; 缓存到redis中, 并记录当前下载文件的信息
        String uuid = DataUtil.getUUID();
        reqDto.setUserId(SecurityUtil.getUserId());
        reqDto.setStatus(AttachmentConstant.DownUseStatus.WAIT.getCode());
        redisCacheUtil.hset(DOWNLOAD_FILE_SESSION_KEY, uuid, reqDto);

        return Result.ok(String.format("%s/attackDefenceTool/file/download?fileToken=%s", contextPath, uuid));
    }

    @Override
    public Map<String, String> attachmentMap(List<String> attachmentIds) {
        Map<String, String> map = new HashMap<>(4);
        if (ObjectUtils.isEmpty(attachmentIds)) {
            return map;
        }
        attachmentIds = attachmentIds.stream().distinct().collect(Collectors.toList());
        return super.listByIds(attachmentIds).stream()
                .collect(Collectors.toMap(attachment -> String.valueOf(attachment.getId()), SysAttachment::getFileName));
    }

    @Override
    public Map<String, Long> uploadImageDirectory(File image, String userId, String fileNo, StringBuilder errorMsg) {
        Map<String, Long> imageMap = new HashMap<>(4);
        uploadImageDirectory(image, userId, fileNo, errorMsg, imageMap);
        return imageMap;
    }

    private void uploadImageDirectory(File image, String userId, String fileNo, StringBuilder errorMsg, Map<String, Long> imageMap) {
        File[] files = image.listFiles();
        if (null == files) {
            return ;
        }

        for (File file: files) {
            // 如果是文件夹，则递归调用
            if (file.isDirectory()) {
                uploadImageDirectory(file, userId, fileNo, errorMsg, imageMap);
                continue;
            }
            // 将图片上传
            Result<Long> result = this.uploadFile(file, userId, fileNo);
            if (result.isSuccess()) {
                imageMap.put(file.getName(), result.getResult());
            } else {
                errorMsg.append(String.format("上传%s图片失败,错误原因: %s", file.getName(), result.getMessage()));
            }
        }

    }

}
